// 题目描述
// 现有一个 N × M 的矩阵，每个单元格包含一个数值，这个数值代表该位置的相对高度。矩阵的左边界和上边界被认为是第一组边界，而矩阵的右边界和下边界被视为第二组边界。
// 矩阵模拟了一个地形，当雨水落在上面时，水会根据地形的倾斜向低处流动，但只能从较高或等高的地点流向较低或等高并且相邻（上下左右方向）的地点。我们的目标是确定那些单元格，从这些单元格出发的水可以达到第一组边界和第二组边界。

// 输入描述
// 第一行包含两个整数 N 和 M，分别表示矩阵的行数和列数。 
// 后续 N 行，每行包含 M 个整数，表示矩阵中的每个单元格的高度。

// 输出描述
// 输出共有多行，每行输出两个整数，用一个空格隔开，表示可达第一组边界和第二组边界的单元格的坐标，输出顺序任意。

// 输入示例
// 5 5
// 1 3 1 2 4
// 1 2 1 3 2
// 2 4 7 2 1
// 4 5 6 1 1
// 1 4 1 2 1

// 输出示例
// 0 4
// 1 3
// 2 2
// 3 0
// 3 1
// 3 2
// 4 0
// 4 1

const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;

// 深度遍历，因为每个dfs的时间复杂度为m*n，又循环了m*n次，所以循环的时间复杂度为m2*n2
void async function () {
    line = await readline()
    let [m, n] = line.split(' ').map((i) => parseInt(i)),
        graph = new Array(m).fill(0).map(() => new Array(n).fill(0)),
        one = 0,
        two = 0;
    // 初始化邻接矩阵
    for (let i = 0; i < m; i++) {
        line = await readline()
        let rowNums = line.split(' ').map((i) => parseInt(i))
        for (let j = 0; j < n; j++) {
            graph[i][j] = rowNums[j]
        }
    }

    for (let i = 0; i < m; i++) {
        for (let j = 0; j < n; j++) {
            let visit = new Array(m).fill(0).map(() => new Array(n).fill(false))
            visit[i][j] = true
            one = two = 0
            let bool = dfs(graph, visit, i, j);
            if (bool) console.log(i+" "+j)
        }
    }

    function dfs(graph, visit, i, j) {
        // 是否遍历过左上边的节点
        if (i === 0 || j === 0) {
            one = 1
        }
        // 是否遍历过右下边的节点
        if (i === graph.length - 1 || j === graph[0].length - 1) {
            two = 1
        }
        if (one + two === 2) return true

        let direction = [[1, 0], [-1, 0], [0, 1], [0, -1]]
        for (let [left, right] of direction) {
            let newI = i + left,
                newJ = j + right;
            if (newI < 0 || newI >= graph.length || newJ < 0 || newJ >= graph[0].length) continue
            if (graph[newI][newJ] <= graph[i][j] && visit[newI][newJ] === false) {
                visit[newI][newJ] = true
                if (dfs(graph, visit, newI, newJ)) return true
            }
        }
        return false
    }
}()






// 利用连个数组，从两边逆流而上，每个visit数组节点访问一次，最多访问m*n次
void async function () {
    line = await readline()
    let [m, n] = line.split(' ').map((i) => parseInt(i)),
        graph = new Array(m).fill(0).map(() => new Array(n).fill(0)),
        visitOne = new Array(m).fill(0).map(() => new Array(n).fill(false)),
        visitTwo = new Array(m).fill(0).map(() => new Array(n).fill(false));
    // 初始化邻接矩阵
    for (let i = 0; i < m; i++) {
        line = await readline()
        let rowNums = line.split(' ').map((i) => parseInt(i))
        for (let j = 0; j < n; j++) {
            graph[i][j] = rowNums[j]
        }
    }

    for (let i = 0; i < m; i++) {
        for (let j = 0; j < n; j++) {
            if (i === 0 || j === 0) {
                dfs(graph, visitOne, i, j)
            }
            if (i === m - 1 || j === n - 1) {
                dfs(graph, visitTwo, i, j)
            }
        }
    }
    for (let i = 0; i < m; i++) {
        for (let j = 0; j < n; j++) {
            if (visitOne[i][j] && visitTwo[i][j]) {
                console.log(i+" "+j)
            }
        }
    }
}()

function dfs(graph, visit, i, j) {
    visit[i][j] = true
    let direction = [[1, 0], [-1, 0], [0, 1], [0, -1]]
    for (let [left, right] of direction) {
        let newI = i + left,
            newJ = j + right;
        if (newI < 0 || newI >= graph.length || newJ < 0 || newJ >= graph[0].length) continue
        if (graph[newI][newJ] >= graph[i][j] && visit[newI][newJ] === false) {
            dfs(graph, visit, newI, newJ)
        }
    }
}

